
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <ctype.h>
#include <mtd/mtd-user.h>
#include <sys/ioctl.h>

#define LINE_END "\n"
#define SPINOR_KO_PATH "/"
#define SIZE_64K (0x10000)
#define SIZE_ALIGN_64K(size) (((size) + SIZE_64K - 1) & (~(SIZE_64K - 1)))

static int util_execute_shell(const char *cmd, char *buf, int len)
{
    int ret = 0, length = 0;
    FILE *fp;

    // 执行预先设定的命令，并读出该命令的标准输出
    // 这时执行shell命令的相关内容不会出现在标准输出上
    fp = popen(cmd, "r");
    if (NULL == fp)
    {
        printf("drv_spinor:popen cmd:%s failure.\n", cmd);
        return -1;
    }

    if (buf != NULL)
    {
        memset(buf, 0x00, len);
        if ((length = fread(buf, 1, len - 1, fp)) < 0)
        {
            printf("drv_spinor:Read from fp failure:%s\n", strerror(errno));
            return -1;
        }
    }
    // cmd执行成功返回值为0
    // cmd执行失败返回值为非0
    // 关闭文件指针
    ret = pclose(fp);
    ret = (length > 0) ? length : ret;
    return ret;
}

static void *buf_strip(char *buf)
{
    int i = 0, j = strlen(buf) - 1;
    char buffer[2048] = {0};
    while (!isgraph(buf[i]))
    {
        i++;
    }

    while (!isgraph(buf[j]))
    {
        j--;
    }
    memcpy(buffer, buf + i, j - i + 1);
    memcpy(buf, buffer, j - i + 2);
    return buf;
}

static int drv_spinor_open(void)
{
    int ret, fd;
    char cmd[128], buf[4096] = {0}, mtd[128], result[64], *p;
    sprintf(cmd, "insmod %sspi-omap2-mcspi.ko", SPINOR_KO_PATH);
    ret = system(cmd);
    if (ret < 0)
    {
        printf("drv_spinor:system %s:%s\n", cmd, strerror(errno));
        return -1;
    }

    fd = open("/proc/mtd", O_RDONLY);
    if (fd < 0)
    {
        printf("drv_spinor:open /proc/mtd failed\n");
        return -1;
    }
    ret = read(fd, buf, sizeof(buf));
    if(ret < 0) {
        printf("drv_spinor:read /proc/mtd failed\n");
        return -1;
    }

    ret = util_execute_shell("cat /proc/mtd | grep spi0.0 | cut -d ':' -f 1", result, sizeof(result));
    if (ret < 0)
    {
        printf("drv_spinor:query spinor mtd failed\n");
        return -1;
    }
    p = buf_strip(result);

    sprintf(mtd, "/dev/%s", p);
    fd = open(mtd, O_RDWR | O_SYNC);
    if (fd < 0)
    {
        printf("drv_spinor:open %s failed\n", mtd);
        return -1;
    }
    return fd;
}

static int drv_spinor_erase(int fd, int size)
{
    //erase fail when not align to 64k
    int align_size = SIZE_ALIGN_64K(size);
    erase_info_t erase;
    erase.start = 0;
    erase.length = align_size;
    if (ioctl(fd, MEMERASE, &erase) == -1)
    {
        printf("drv_spinor:mtd ioctl memerase failed\n");
        return -1;
    }
    return 0;
}
#if 0
static int drv_spinor_read(int fd, char *buff, int size)
{
    return read(fd, buff, size);
}
#endif 

static int drv_spinor_write(int fd, char *buff, int size)
{
    char *buff_read = NULL;
    int ret = -1;
    ret = drv_spinor_erase(fd, size);
    if (ret != 0)
    {
        printf("drv_spinor:spinor erase failed\n");
        goto failed;
    }

    ret = write(fd, buff, size);
    if (ret != size)
    {
        printf("drv_spinor:spinor write failed\n");
        goto failed;
    }

    buff_read = malloc(size);
    if (buff_read == NULL)
    {
        printf("drv_spinor:spinor write malloc failed\n");
        goto failed;
    }
    lseek(fd, 0, SEEK_SET);
    ret = read(fd, buff_read, size);
    if (ret != size)
    {
        printf("drv_spinor:spinor read failed\n");
        goto failed;
    }

    if (memcmp(buff, buff_read, size) != 0)
    {
        printf("drv_spinor:spinor verify failed\n");
        goto failed;
    }
    printf("drv_spinor:spinor write and read success\n");
    ret = 0;
failed:
    if (buff_read)
    {
        free(buff_read);
    }
    return ret;
}

static int drv_spinor_close(int fd)
{
    int ret;
    if (fd > 0)
        close(fd);
    char *cmd = "rmmod spi-omap2-mcspi.ko";
    ret = system(cmd);
    if (ret < 0)
    {
        printf("drv_spinor:rmmod spinor failed\n");
        return -1;
    }
    return 0;
}

int drv_spinor_write_file(char *path)
{
    int fd = 0, fd_mtd = 0, ret = -1, size;
    struct stat st;
    char *buff_firmware = NULL;
    stat(path, &st);

    size = st.st_size;
    buff_firmware = malloc(size);
    if (buff_firmware == NULL)
    {
        printf("drv_spinor:malloc for firmware failed\n");
        goto fail;
    }

    fd_mtd = drv_spinor_open();
    if (fd_mtd < 0)
    {
        printf("drv_spinor:open spidev failed\n");
        goto fail;
    }

    fd = open(path, O_RDONLY);
    if (fd < 0)
    {
        printf("drv_spinor:open firmware:%s failed\n", path);
        goto fail;
    }
    ret = read(fd, buff_firmware, size);
    if (ret < 0)
    {
        printf("drv_spinor:read firmware:%s failed\n", path);
        goto fail;
    }

    ret = drv_spinor_write(fd_mtd, buff_firmware, size);
    if (ret < 0)
    {
        printf("drv_spinor:write firmware to spidev failed\n");
        goto fail;
    }
    ret = 0;
fail:
    if (buff_firmware)
        free(buff_firmware);

    if (fd_mtd > 0)
    {
        drv_spinor_close(fd_mtd);
    }

    if (fd > 0)
    {
        close(fd);
    }
    return ret;
}

#if 0
int main()
{
    char path[128] = {0};
    printf("input firmware path\n");
    scanf("%s", path);
    drv_spinor_write_file(path);
    return 0;
}
#endif
 
